<h4>PCI DSS</h4>

The "Payment Card Industry Data Security Standard" (<a href="http://en.wikipedia.org/wiki/Payment_Card_Industry_Data_Security_Standard">PCI DSS</a>) advocates common sense policies for building a secure network and protecting our data. Actually every enterprise should adopt PCI DSS because it's the only and best such thing we got. Where it refers to "cardholder data," replace that with "identity data" or "access credentials" or what-have-you. 

If we want information security, then PCI DSS is our new best friend forever, albeit a high-maintenance one.

<img align=left style="margin-right: 16px" src="http://jroller.com/evanx/resource/Gnome-application-certificate-250-crop.png">

PCI DSS suggests encrypting our data-encryption key (DEK) in order to protect it. Great, we now have a "key-encryption key" (KEK) that requires even more protection ;)

Furthermore, PCI DSS decrees that manual key management requires "split knowledge and dual control" e.g. for key generation and loading. The intent is that no single person can extract the clear-text data. 

The glaring problem is that sysadmins are a single person, with god-like access to all our data, and de facto custodian of the proverbial keys to the kindgom. <i>Consequently sysadmins have root access ;)</i>

<h4>Solution overview</h4>

We'll store our key in a standard <a href="http://www.docjar.com/html/api/com/sun/crypto/provider/JceKeyStore.java.html">JceKeyStore</a>, whose <a href="http://www.docjar.com/html/api/com/sun/crypto/provider/KeyProtector.java.html">KeyProtector</a> uses a <a href="http://www.docjar.com/html/api/com/sun/crypto/provider/PBEWithMD5AndTripleDESCipher.java.html">Triple DES cipher</a> for password-based encryption of the key.

We'll split the knowledge of the key-protection password between two custodians, so it's known to no single person. Clearly dual control by those two custodians is then required to load the key, and PCI DSS compliance is thus enabled.

We propose enrolling at least three custodians, so that if one is unavailable, we're still good to go with two others. For each duo, we'll store a copy of the key protected by the concatenation of their two personal passwords. 

Since the split password is used for password-based encryption of the key, it is effectively a key-encryption key, which is clearly well protected by virtue of being known to no single person, and not stored anywhere.

<h4>DualControlGenSecKey demo</h4>

Step 1 for any data security endeavour is to generate an encryption key. Whereas keytool prompts for a password entered by a single custodian, we introduce DualControlGenSecKey to handle multiple password submissions via SSL.

<pre>
$ java -Ddualcontrol.submissions=3 -Ddualcontrol.minPassphraseLength=8 \
    -Ddualcontrol.ssl.keyStore=keystores/dualcontrolserver.jks \
    -Ddualcontrol.ssl.trustStore=keystores/dualcontrolserver.trust.jks \
    -Dkeystore=keystores/dek2013.jceks -Dstoretype=JCEKS \
    -Dalias=dek2013 -Dkeyalg=AES -Dkeysize=256 \
    dualcontrol.DualControlGenSecKey

Enter passphrase for dualcontrol.ssl:
Enter passphrase for keystore for new key dek2013: 
</pre>

where we have requested a new 256bit AES secret key, aliased as dek2013.

Note that the -Ddualcontrol.ssl.keyStore property is the location of the "private keystore" for SSL. This contains the <i>private</i> key and its public certificate for RSA assymmetric encryption to establish an SSL connection. This should not be confused with our "secret keystore" which contains an AES or DESede <i>secret</i> key for symmetric encryption of our data.

The logs produced by DualControlGenSecKey are as follows.

<pre>
INFO [DualControlManager] purpose: new key dek2013
INFO [DualControlManager] accept: 3
INFO [DualControlManager] Received evanx
INFO [DualControlManager] Received henty
INFO [DualControlManager] Received brent
INFO [DualControlManager] dualAlias: brent-evanx
INFO [DualControlManager] dualAlias: brent-henty
INFO [DualControlManager] dualAlias: evanx-henty
INFO [DualControlGenSecKey] alias: dek2013-brent-evanx
INFO [DualControlGenSecKey] alias: dek2013-brent-henty
INFO [DualControlGenSecKey] alias: dek2013-evanx-henty
</pre>

where DualControlManager does the leg work of receiving passwords from custodians.

For this demo, three custodians submit their passwords via SSL sockets, using DualControlConsole as shown below, where they are identified as evanx, henty and brent by their SSL client certs' CN field.

<pre>
evanx$ java -Ddualcontrol.ssl.keyStore=keystores/evanx.jks dualcontrol.DualControlConsole
Enter passphrase for dualcontrol.ssl:
Connected evanx
Enter passphrase for new key dek2013:
Re-enter passphrase:
Received evanx
</pre>

We see that DualControlGenSecKey creates secret key entries under the following "dual aliases."

<pre>
$ keytool -keystore keystores/dek2013.jceks -storetype JCEKS -list
Enter keystore password:  

Keystore type: JCEKS
Keystore provider: SunJCE

Your keystore contains 3 entries

dek2013-brent-henty, 27 Sep 2013, SecretKeyEntry, 
dek2013-brent-evanx, 27 Sep 2013, SecretKeyEntry, 
dek2013-evanx-henty, 27 Sep 2013, SecretKeyEntry, 
</pre>

Actually these three keys are one and the same! However each copy has a different "split password," which is a combination of a pair of personal passwords. Consequently the key password is known to no single person. The keystore password is shared, but that's just extra.

<h4>DualControlGenSecKey implementation</h4>

Let's walk through the code.

<pre>
public class DualControlGenSecKey {
    private int submissionCount;
    private String keyAlias;
    private String keyStoreLocation;
    private String keyStoreType;
    private String keyAlg;
    private int keySize;
    private char[] keyStorePassword;
    private Map<String, char[]> dualMap;
    private SSLContext sslContext;

    public DualControlGenSecKey(Properties properties, MockableConsole console) {
        this.props = new ExtendedProperties(properties);
        this.console = console;
        submissionCount = props.getInt("dualcontrol.submissions", 3);
        keyAlias = props.getString("alias");
    }
    ...
}
</pre>
where we configure via Properties and provide a MockableConsole for entering keystore passwords. 

Note that ExtendedProperties is a utility wrapper for java.util.Properties that will throw an exception if the property is not found and no default is provided.

We initialise the instance with an SSLContext as follows.
<pre>
    public void init() throws Exception {
        sslContext = SSLContexts.create(true, "dualcontrol.ssl", props, console);
    }
</pre>

where we'll see further below that SSLContexts uses a keystore and truststore configured by properties e.g. dualcontrol.ssl.keyStore and dualcontrol.ssl.trustStore. These SSL keystores should not be confused with the keystore for our new secret key which we are generating.

Our main() method below passes System properties i.e. -D options, and the System console.
<pre>
    public static void main(String[] args) throws Exception {
        DualControlGenSecKey instance = new DualControlGenSecKey(System.getProperties(),
                new MockableConsoleAdapter(System.console()));
        try {
            instance.init();
            instance.call();
        } catch (DualControlException e) {
            instance.console.println(e.getMessage());
        } finally {
            instance.clear();
        }
    }
</pre>
where we instantiate, initialise, and then call this class.

<pre>
    public void call() throws Exception {
        keyStoreLocation = props.getString("keystore");
        if (new File(keyStoreLocation).exists()) {
            throw new Exception("Keystore file already exists: " + keyStoreLocation);
        }
        keyStorePassword = props.getPassword("storepass", null);
        if (keyStorePassword == null) {
            keyStorePassword = console.readPassword(
                    "Enter passphrase for keystore for new key %s: ", keyAlias);
            if (keyStorePassword == null) {
                throw new Exception("No keystore passphrase from console");
            }
        }
        KeyStore keyStore = createKeyStore();
        keyStore.store(new FileOutputStream(keyStoreLocation), keyStorePassword);
    }

    public KeyStore createKeyStore() throws Exception {
        String purpose = "new key " + keyAlias;
        DualControlManager manager = new DualControlManager(properties, 
                submissionCount, purpose);
        manager.init(sslContext);
        manager.call();
        return buildKeyStore(manager.getDualMap());
    }
</pre>
where DualControlManager provides a map of dual aliases and passwords, composed from submissions via SSL. We pass this map to the buildKeyStore() method below.
<pre>
    public KeyStore buildKeyStore(Map<String, char[]> dualMap) throws Exception {
        keyAlias = props.getString("alias");
        keyStoreType = props.getString("storetype");
        keyAlg = props.getString("keyalg");
        keySize = props.getInt("keysize");
        KeyGenerator keyGenerator = KeyGenerator.getInstance(keyAlg);
        keyGenerator.init(keySize);
        SecretKey secretKey = keyGenerator.generateKey();
        KeyStore keyStore = KeyStore.getInstance(keyStoreType);
        keyStore.load(null, null);
        setEntries(keyStore, secretKey, keyAlias, dualMap);
        return keyStore;
    }

    private static void setEntries(KeyStore keyStore, SecretKey secretKey,
            String keyAlias, Map<String, char[]> dualMap) throws Exception {
        KeyStore.Entry entry = new KeyStore.SecretKeyEntry(secretKey);
        for (String dualAlias : dualMap.keySet()) {
            char[] dualPassword = dualMap.get(dualAlias);
            String alias = keyAlias + "-" + dualAlias;
            logger.info("alias: " + alias);
            KeyStore.PasswordProtection passwordProtection =
                    new KeyStore.PasswordProtection(dualPassword);
            keyStore.setEntry(alias, entry, passwordProtection);
            passwordProtection.destroy();
            Arrays.fill(dualPassword, (char) 0);
        }
    }
</pre>
where for each duo in the map we program a keystore entry containing the same key, but protected by a different split password.

Incidently, we invariably use char arrays for passwords, and clear these as soon as possible. String's are immutable and will be garbage-collected and overwritten in memory at some stage, but that is too indeterminate to alleviate our paranoia. Having said that, <a href="http://www.docjar.com/html/api/javax/crypto/spec/SecretKeySpec.java.html">SecretKeySpec</a>.getEncoded() clones the byte array, which is somewhat disconcerting. So hopefully no one can scan or "debug" our JVM memory and thereby extract our passwords, or indeed the key itself.

Some argue that one should not write code per se, but rather tests with accompanying code, hand in glove. Indeed we observe that our implementation is well defined by the unit tests.
<pre>
    @Test
    public void testGenKeyStore() throws Exception {
        dualMap.put("brent-evanx", "bbbb|eeee".toCharArray());
        dualMap.put("brent-henty", "bbbb|hhhh".toCharArray());
        dualMap.put("evanx-henty", "eeee|hhhh".toCharArray());
        MockConsole appConsole = new MockConsole("app", keyStorePass);
        DualControlGenSecKey instance = new DualControlGenSecKey(properties, appConsole);
        KeyStore keyStore = instance.buildKeyStore(dualMap);
        Assert.assertEquals(3, Collections.list(keyStore.aliases()).size());
        Assert.assertEquals("dek2013-brent-evanx", asSortedSet(keyStore.aliases()).first());
        SecretKey key = getSecretKey(keyStore, "dek2013-brent-evanx", "bbbb|eeee".toCharArray());
        Assert.assertEquals("AES", key.getAlgorithm());
        Assert.assertTrue(Arrays.equals(key.getEncoded(), getSecretKey(keyStore, 
                "dek2013-brent-henty", "bbbb|hhhh".toCharArray()).getEncoded()));
    }
</pre>
where we build a map of sample dual submissions, and inspect the KeyStore returned by buildKeyStore().

Note that the key passwords are concatenations of personal passwords, where we arbitrarily choose the vertical bar character as a delimiter. This is our so-called split password, which can be used to recover the key in clear-text using the following utility method.
<pre>
    private static SecretKey getSecretKey(KeyStore keyStore, String keyAlias, char[] keyPass) 
            throws GeneralSecurityException {
        KeyStore.SecretKeyEntry entry = (KeyStore.SecretKeyEntry) keyStore.getEntry(
                keyAlias, new KeyStore.PasswordProtection(keyPass));
        return entry.getSecretKey();
    }
</pre>
which invokes KeyStore.getEntry().

<h4>SSLContexts</h4>

Similarly to the standard -Dnet.javax.ssl.keyStore <i>et al</i> command-line options used to specify the default keystore and truststore for SSL sockets, and in order to avoid any potential conflict with those, we use -Ddualcontrol.ssl.keyStore <i>et al</i>. These properties are used to create an SSLContext as follows.
<pre>
public class SSLContexts {    

    public static SSLContext create(boolean strict, String sslPrefix, Properties properties,
            MockableConsole console) throws Exception {
        ExtendedProperties props = new ExtendedProperties(properties);
        sslPrefix = props.getString(sslPrefix, sslPrefix);
        String keyStoreLocation = props.getString(sslPrefix + ".keyStore");
        if (keyStoreLocation == null) {
            throw new Exception("Missing keystore property for " + sslPrefix);
        }
        char[] pass = props.getPassword(sslPrefix + ".pass", null);
        if (pass == null) {
            pass = console.readPassword("Enter passphrase for %s: ", sslPrefix);
        }
        String trustStoreLocation = props.getString(sslPrefix + ".trustStore", 
                keyStoreLocation);
        if (strict && keyStoreLocation.equals(trustStoreLocation)) {
            throw new KeyStoreException("Require separate truststore");
        }
        SSLContext sslContext = create(keyStoreLocation, pass, trustStoreLocation);
        Arrays.fill(pass, (char) 0);
        return sslContext;
    }
    ...
}
</pre>
where we reuse the same password for the SSL keystore, its private key, and the truststore. This password can specified on the command-line e.g. for automated test scripts, but otherwise we prompt for the SSL keystore password to be entered on the console. Moreover, we have introduced a MockableConsole for the benefit of automated unit testing.

Note that the truststore is defaulted to the specified private keystore, which is usually only OK on the client side, when using self-signed certificates. When using CA-signed certificates, our keystore must contain the certificate chain including the CA certs e.g. intermediate and root. However, these cannot be in our truststore, otherwise we trust any cert signed by that CA!

The server SSLContext must have a separate truststore, even when using self-signed certificates. Otherwise a rogue client certificate can be created by signing it with the server certificate.

Ordinarily we create the SSLContext as follows.
<pre>
    public static SSLContext create(KeyStore keyStore, char[] keyPassword,
            KeyStore trustStore) throws Exception {
        KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance("SunX509");
        keyManagerFactory.init(keyStore, keyPassword);
        TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance("SunX509");
        trustManagerFactory.init(trustStore);
        SSLContext sslContext = SSLContext.getInstance("TLS");
        sslContext.init(keyManagerFactory.getKeyManagers(),
                trustManagerFactory.getTrustManagers(), new SecureRandom());
        return sslContext;
    }
</pre>

Incidently, in a further article we'll implement a custom X509TrustManager to support local certificate revocation. This enables a simple "local CA" where the server cert is also our root CA cert used to sign client certs.
<pre>
public class RevocableClientTrustManager implements X509TrustManager {
    X509Certificate serverCertificate;
    X509TrustManager delegate;
    Collection<String> revokedCNList;
    Collection<BigInteger> revokedSerialNumberList;
    ...
    @Override
    public void checkClientTrusted(X509Certificate[] certs, String authType) 
            throws CertificateException {
        if (certs.length != 2) {
            throw new CertificateException("Invalid cert chain length");
        }
        if (!certs[0].getIssuerX500Principal().equals(
                serverCertificate.getSubjectX500Principal())) {
            throw new CertificateException("Untrusted issuer");
        }
        if (!Arrays.equals(certs[1].getPublicKey().getEncoded(),
                serverCertificate.getPublicKey().getEncoded())) {
            throw new CertificateException("Invalid server certificate");
        }
        if (revokedCNList != null && 
                revokedCNList.contains(getCN(certs[0].getSubjectDN()))) {
            throw new CertificateException("Certificate CN revoked");
        }
        if (revokedSerialNumberList != null &&
                revokedSerialNumberList.contains(certs[0].getSerialNumber())) {
            throw new CertificateException("Certificate serial number revoked");
        }
        delegate.checkClientTrusted(certs, authType);
    }
}
</pre>
where we initialise this trust manager with a collection of revoked certificates' CNs or serial numbers. We check that the client certificate is signed by our server certificate, and not revoked. Finally, we delegate to the standard X509TrustManager for good measure.

<h4>keytool</h4>

Naturally we use keytool to create our private SSL keystore, e.g. as required by DualControlConsole, specified by our dualcontrol.ssl.keyStore property.
<pre>
$ keytool -keystore evanx.jks -genkeypair -keyalg rsa -keysize 2048 -validity 365 -alias evanx \
    -dname "CN=evanx, OU=test"
</pre>
We export our certificate as follows.
<pre>
$ keytool -keystore evanx.jks -alias evanx -exportcert -rfc
</pre>
We cut and paste the exported PEM text into a file, which we can inspect using openssl as follows.
<pre>
$ openssl x509 -text -in evanx.pem
Certificate:
    Data:
        Version: 3 (0x2)
        Serial Number: 1380030508 (0x5241982c)
    Signature Algorithm: sha1WithRSAEncryption
        Issuer: CN=evanx, OU=test
        Validity
            Not Before: Sep 24 13:48:28 2013 GMT
            Not After : Sep 24 13:48:28 2014 GMT
        Subject: CN=evanx, OU=test
        Subject Public Key Info:
            Public Key Algorithm: rsaEncryption
                Public-Key: (2048 bit)
</pre>
We import the cert into the server SSL truststore as required by DualControlManager on behalf of DualControlGenSecKey and our app.
<pre>
$ keytool -keystore dualcontrolserver.trust.jks -alias evanx -importcert -file evanx.pem
</pre>

Similarly, the server cert is imported into the custodians' truststores as specified by dualcontrol.ssl.trustStore for DualControlConsole.

<h4>Client certificate signing</h4>

Incidently, we could export an CSR, and sign this with the server cert. In this case our server is a local CA, which must support certificate revocation.
<pre>
$ keytool -keystore evanx.jks -alias evanx -certreq
</pre>
We use Java7's keytool to sign the CSR.
<pre>
$ keytool -keystore dualcontrolserver.jks -gencert -validity 365 -rfc \
    -dname "CN=evanx, OU=test" -infile evanx.csr -outfile evanx.signed.pem
</pre>
Note that -gencert is not available in Java6's keytool.

We inspect the cert using openssl.
<pre>
$ openssl x509 -text -in evanx.signed.pem | grep CN
        Issuer: CN=dualcontrolserver, OU=test
        Subject: CN=evanx, OU=test
</pre>
Since our keystore requires our cert chain to be imported in the correct order starting with the root cert, we import the server cert first, and then our signed cert.
<pre>
$ keytool -keystore evanx.jks -importcert -noprompt \
    -file dualcontrolserver.pem -alias dualcontrolserver 
Enter keystore password:  
Certificate was added to keystore

$ keytool -keystore evanx.jks -importcert -noprompt \
    -file evanx.signed.pem  -alias evanx
Enter keystore password:  
Certificate reply was installed in keystore
</pre>

Our client keystore can double up as our truststore since it contains the server cert as the root of its certificate chain. If our server cert is CA-signed, we can't do that, since our keystore certificate chain would include the CA's root cert, and so we would trust <i>any</i> cert signed by that CA.

Our server keystore can also double up as its truststore, since it contains its cert which signs our client certs.

<h4>Client certificate management</h4>

Clearly a rogue certificate with a duplicate CN can impersonate a valid certificate. So when issuing a certificate, we must take care to ensure the uniqueness of the CN, and add the certificate, or at least its unique identifier, to a registry of some sort. 

If a certificate is signed but not recorded, or its record is deleted, our server is forever vulnerable to that rogue certificate. We want to regularly review and revoke access, and so require a record of all issued certificates. 

We might record our signed certs into a keystore file as follows.
<pre>
$ keytool -keystore dualcontrolserver.issued.jks -importcert -alias evanx -file evanx.pem 
Certificate was added to keystore
</pre>
where this is not a truststore per se, but just a database of issued certificates. 

Interestingly, in trying to avoid a truststore containing all our client certificates, we have nevertheless ended up with one! 

All told, given the risk of a rogue certificate, we recommend sticking with self-signed client certificates, explicitly imported into our server truststore. Crucially, its keystore must not be used as its truststore, since the keystore naturally contains the server certificate, and so a rogue certificate can be created by signing it with the server key, e.g. using keytool -gencert.

<h4>DualControlConsole</h4>

Let's try our DualControlConsole app, e.g. to submit a password to DualControlGenSecKey.

<pre>
evanx$ java -Ddualcontrol.ssl.keyStore=keystores/evanx.jks dualcontrol.DualControlConsole
Enter passphrase for dualcontrol.ssl:
Connected evanx
Enter passphrase for new key dek2013: 
Re-enter passphrase: 
Received evanx
</pre>
where SSLContexts prompts for the SSL keystore password first. We then enter a passphrase for the new key.

The importance of not echoing passwords to the console, so that they are not cut and pasted by mistake, is illustrated above ;)

We implement the main() as follows.
<pre>
public class DualControlConsole {
    Properties properties;
    MockableConsole console;
    SSLContext sslContext;

    public static void main(String[] args) throws Exception {
        DualControlConsole instance = new DualControlConsole(System.getProperties(), 
                new MockableConsoleAdapter(System.console()));
        try {
            instance.init();
            instance.call();
        } finally {            
            instance.clear();
        }
    }

    public void init() throws Exception {
        init(SSLContexts.create(false, "dualcontrol.ssl", properties, console));
    }
    ...
}
</pre>

where we create an SSLContext from the provided properties, which must specify the requisite keystore and truststore. (Later we'll see that our unit test provides an SSLContext from KeyStore's which it creates programmatically.)

We submit the password via SSL in the call() method below.

<pre>
    private final static int PORT = 4444;
    private final static String HOST = "127.0.0.1";
    
    public void call() throws Exception {
        Socket socket = sslContext.getSocketFactory().createSocket(HOST, PORT);
        DataInputStream dis = new DataInputStream(socket.getInputStream());
        String message = dis.readUTF();
        console.println(message);
        String purpose = dis.readUTF();
        if (purpose.length() == 0) {
            return;
        }
        DataOutputStream dos = new DataOutputStream(socket.getOutputStream());
        char[] password = console.readPassword(
                "Enter passphrase for " + purpose + ": ");
        String invalidMessage = new DualControlPassphraseVerifier(properties).
                getInvalidMessage(password);
        if (invalidMessage != null) {
            console.println(invalidMessage);
            dos.writeShort(0);
        } else {
            char[] pass = console.readPassword(
                    "Re-enter passphrase: ");
            if (!Arrays.equals(password, pass)) {
                console.println("Passwords don't match.");
                dos.writeShort(0);
            } else {
                writeChars(dos, password);
                String message = dis.readUTF();
                console.println(message);
            }
            Arrays.fill(pass, (char) 0);
        }
        Arrays.fill(password, (char) 0);
        socket.close();
    }
</pre>

where if the re-entered password does not match, we politely send an empty password to the server, which indicates an aborted attempt. Similarly, if the server rejects our connection, it will politely send us an empty purpose, so that we can abort gracefully.

Note that have hard-coded the host to localhost to enforce ssh port forwarding for remote access i.e. SSL over ssh :)

A known "bug" is that more godly sysadmins can socially-engineer other less godly admins to submit their passwords whilst a malicious socket server has been installed by the most godly one onto that frikking port! Other naughty tricks that come to mind is the creation of phantom custodians, or the impersonation of other custodians e.g. during a key generation procedure, by abusing root access to SSL keystores. So we must ensure that our logs and alerts prevent such events from going unnoticed.

<h4>Multi-factor authentication</h4>

On a positive note, we can setup "<i>double</i>-two-factor" authentication whereby the client requires a password-protected ssh key for port forwarding, and a password-protected KeyStore for the client-authenticated SSL connection. 

<img align="right" src="http://jroller.com/evanx/resource/Gnome-preferences-desktop-personal-250-crop.png"/>

For both the underlying ssh access, and the SSL connection over that, the custodian needs to <i>have</i> the private key, and <i>know</i> the password that is protecting it, and so arguably both require <a href="http://en.wikipedia.org/wiki/Multi-factor_authentication">multi-factor authentication</a>. 

However root can also "have" the files containing the keys, but at least doesn't know other custodians' passwords protecting the keys therein.

Some argue that to really "have" something, it should not be so copyable as a key in a file, but rather it should be a hardware token or smartcard. However, <a href="https://www.pcisecuritystandards.org/documents/navigating_dss_v20.pdf"><i>Navigating PCI DSS 2.0</i></a> provides the following guidance: 
<blockquote>
<i>A digital certificate is a valid option as a form of the authentication type "something 
you have" as long as it is unique.</i>
</blockquote>

A further challenge is that the existence of a password which protects an ssh or SSL key, cannot be verified by the server. So at least we double up to mitigate this ;)

<h4>Password complexity vs length</h4>

We ensure that the complexity and/or length of the password is sufficient to counter brute-force attacks.

<pre>
public class DualControlPassphraseVerifier {
    private final boolean verifyPassphrase;
    private final boolean verifyPassphraseComplexity;
    private final int minPassphraseLength;
    private final int minWordCount;

    public DualControlPassphraseVerifier(Properties properties) {
        ExtendedProperties props = new ExtendedProperties(properties);
        verifyPassphrase = props.getBoolean(
                "dualcontrol.verifyPassphrase", true);
        verifyPassphraseComplexity = props.getBoolean(
                "dualcontrol.verifyPassphraseComplexity", true);
        minPassphraseLength = props.getInt(
                "dualcontrol.minPassphraseLength", 12);
        minWordCount = props.getInt(
                "dualcontrol.minWordCount", 4);
    }
    ...
</pre>

We note that PCI DSS mandates alphanumeric passwords, whereas <a href="http://en.wikipedia.org/wiki/Sarbanes%E2%80%93Oxley_Act">SOX</a> requires uppercase and lowercase, and we should cover all bases.
<pre>
    public String getInvalidMessage(char[] password) {
        if (verifyPassphrase) {
            if (password.length < minPassphraseLength) {
                return "Passphrase too short";
            }
            if (countWords(password) < minWordCount) {
                return "Too few words in passphrase";
            }
            if (verifyPassphraseComplexity) {
                if (!containsUpperCase(password) || !containsLowerCase(password) || 
                        !containsDigit(password) || !containsPunctuation(password)) {
                    return "Insufficient password complexity";
                }
            }
        }
        return null;
    }
</pre>

Clearly passphrases are easier to remember than complex passwords, and so perhaps we should enforce passphrases with a minimum word count, rather than complexity per se. Then we don't have to write down our complex password and stick it on our monitor.

<img align="right" style="margin-left: 4px" src="http://jroller.com/evanx/resource/wooden-shield200.png" />

Having said that, we can easily capitalise the first letter of our passphrase, add a punctuation mark at the end at least, and somewhere replace an 'o' with a zero or an 'e' with 3. <i>Yeah b4by! (Darn, now I can't use that one anymore.)</i>

Incidently, when we are submitting existing passwords using DualControlConsole to start our app, we might have to disable passphrase verification if our old password falls short of revised complexity requirements. However in that case, we should rather change our password accordingly, e.g. together with a key rotation for good measure.

We note that PCI DSS mandates that passwords be changed every 90 days. However, we hope that this applies to remote access passwords, and not to key-protection passwords. We might argue that key-protection passwords are key-encryption keys, and not remote access credentials per se, even when they protect remote access keys. Actually we argue the opposite in favour of multi-factor authentication, so it depends on the context ;)

<h4>Brutish Key Protector</h4>

Our keystore is unfortunately forever vulnerable to theft and/or brute-force attacks. Even when we have deleted it, someone else might have ill-gotten it earlier. On the upside, we might assume that after say 30 years the data is no longer of any value to anyone.

We observe that an Intel i5 can manage about 30 guesses per millisecond on a JCE keystore, using 4 threads to utilise its quad-cores.

<pre>
evanx@beethoven:~$ java dualcontrol.JCEKSBruteForceTimer 4 1000000 \
  keystores/dek2013.jceks "$pass" dek2013-evanx-henty eeeehhhh

threads 4, count 1000000, time 128s, avg 0.032ms
31 guesses per millisecond
</pre>

If we assume an average of 10 guesses per core per millisecond, and consider a botnet with 1 million cores, then by my backroom calculations, 12 days are required to try all possible passwords up to 10 characters in length using a lazy subset of 40 characters. 

<pre>
guess=10
mach=1000*1000
40^10/(mach*guess*1000*60*60*24)
12 days
</pre>

Considering that the most common 100 words make up a half of all written material (cit. <a href="http://en.wikipedia.org/wiki/Most_common_words_in_English">Wikipedia</a>), if we guess combinations of the 1500 most common words, then 8 days are required for such passphrases with 5 words. 

<pre>
(1500^5)/(mach*guess*1000*60*60*24)
8 days
</pre>

Note that our mandatory minimums apply to each custodian's passphrase, to protect against a rogue custodian perpetrating the brute-force attack on the other half of the split password.

A follow-up article will discuss this further, and conclude that we might want an even stronger KeyStore implementation than JCEKS, in particular with a stronger KeyProtector using PBKDF2 or even scrypt. Then we would specify huge number of iterations e.g. 500k, so that it takes a few seconds to load the key. That is still a tolerable startup delay in our production environment, but would thwart brute-force attacks.

<h4>DualControlDemoApp</h4>

The downside of all this malarkey, is that we have to re-engineer our application to be dual-controlled, in order to load the key it so desperately needs to cipher our data.

<pre>
public class DualControlDemoApp {
    private SecretKey dek;     
    ...
    public void loadKey(String keyStoreLocation, String alias) throws Exception {
        char[] storePass = System.console().readPassword(
                "Enter keystore password for %s: ", alias);
        dek = DualControlSessions.loadKey(keyStoreLocation, "JCEKS", storePass, alias,
                "DualControlDemoApp");
        logger.info("loaded key {}: alg {}", alias, dek.getAlgorithm());
    }
}
</pre>
where we prompt for the shared keystore password to be entered on the console.
<pre>
$ java -Ddualcontrol.ssl.keyStore=keystores/dualcontrolserver.jks \
    -Ddualcontrol.ssl.trustStore=keystores/dualcontrolserver.trust.jks \
    dualcontrol.DualControlDemoApp keystores/dek2013.jceks dek2013
Enter passphrase for dualcontrol.ssl:
Enter keystore password for dek2013:
INFO [DualControlManager] purpose: key dek2013 for DualControlDemoApp
INFO [DualControlManager] accept: 2
</pre>

The application's execution is then blocked whilst we are waiting on the SSLServerSocket for two custodians to submit their passwords using DualControlConsole.
<pre>
evanx$ java -Ddualcontrol.ssl.keyStore=evanx.jks dualcontrol.DualControlConsole
Enter passphrase for dualcontrol.ssl:
Connected evanx
Enter passphrase for key dek2013 for DualControlDemoApp: 
Re-enter passphrase:
Received evanx
</pre>
where we specify the keystore for the SSL socket, which can double up as our truststore.
<pre>
henty$ java -Ddualcontrol.ssl.keyStore=henty.jks dualcontrol.DualControlConsole
Enter passphrase for dualcontrol.ssl:
Connected henty
Enter passphrase for key dek2013 for DualControlDemoApp: 
Re-enter passphrase:
Received henty
</pre>

We observe the following logs from DualControlDemoApp.
<pre>
INFO [DualControlManager] Received evanx
INFO [DualControlManager] Received henty
INFO [DualControlManager] dualAlias: evanx-henty
INFO [DualControlSessions] dek2013-evanx-henty
INFO [DualControlDemoApp] loaded key dek2013: alg AES
</pre>
where it accepts submissions from evanx and henty courtesy of DualControlManager, and can then load the key.

<h4>DualControlSessions</h4>

Our demo app above invokes the loadKey() method below to do the legwork. 

<pre>
public class DualControlSessions {

    public static SecretKey loadKey(String keyStoreLocation, String keyStoreType, 
            char[] keyStorePass, String keyAlias, String purpose) throws Exception {
        KeyStore keyStore = DualControlKeyStores.loadKeyStore(keyStoreLocation, 
                keyStoreType, keyStorePass);
        purpose = "key " + keyAlias + " for " + purpose;
        Map.Entry<String, char[]> entry = DualControlManager.readDualEntry(purpose);
        String dualAlias = entry.getKey();
        char[] splitPassphrase = entry.getValue();
        keyAlias = keyAlias + "-" + dualAlias;
        SecretKey key = (SecretKey) keyStore.getKey(keyAlias, splitPassword);
        Arrays.fill(splitPassword, (char) 0);
        return key;
    }
}
</pre>

where we read the dual info using DualControlManager, which opens an SSLServerSocket and waits for split password submissions from any two custodians.

Note that we append the dual alias e.g. so that dek2013 becomes dek2013-evanx-henty, and get that copy of the key from the keystore.

Once we have used the split password to load the key, we clear that password. However the key itself is now in memory in clear-text, and we must be wary of it being compromised by our application, or extracted by attaching a debugger to the JVM, or by a malicious memory scanner.

We instantiate, initialise and call DualControlManager as follows.
<pre>
    public static Map.Entry<String, char[]> readDualEntry(String purpose) throws Exception {
        DualControlManager manager = new DualControlManager(System.getProperties(), 2, purpose);
        manager.setVerifyPassphrase(false);
        manager.init(new MockableConsoleAdapter(System.console()));
        manager.call();
        return manager.getDualMap().entrySet().iterator().next();
    }   
</pre>

where we read the first entry in dualMap. Actually this is the only entry in the map since we have required only two submissions.

Note that we are accepting custodians' existing passwords to load the key, and so we don't verify their complexity as we would for DualControlGenSecKey.

<h4>Remote keystore</h4>

We might wish to store our keystore centrally, or internally on a more secure server, and load it via SSL as follows. 

<pre>
public class DualControlKeyStores {

    public static KeyStore loadKeyStore(String keyStoreLocation, String keyStoreType,
            char[] keyStorePassword) 
            throws Exception {
        KeyStore keyStore = KeyStore.getInstance(keyStoreType);
        if (keyStoreLocation.contains(":")) {
            String[] array = keyStoreLocation.split(":");
            String keyStoreHost = array[0];
            int keyStorePort = Integer.parseInt(array[1]);
            SSLContext sslContext = SSLContexts.create(false, "fileclient.ssl", 
                    System.getProperties(), 
                    new MockableConsoleAdapter(System.console()));
            Socket socket = sslContext.getSocketFactory().createSocket(
                    keyStoreHost, keyStorePort);
            keyStore.load(socket.getInputStream(), keyStorePassword);
            socket.close();
        } else if (new File(keyStoreLocation).exists()) {
            FileInputStream fis = new FileInputStream(keyStoreLocation);
            keyStore.load(fis, keyStorePassword);
            fis.close();
        } else {
            keyStore.load(null, null);
        }
        return keyStore;
    }
</pre>

where if the keystore location is formatted as host:port, then we open an SSLSocket from which to read a remote keystore file. 

Note that we use the properties fileclient.ssl.keyStore <i>et al</i> for SSLContexts to configure this client SSL connection.

The following utility demonstrates a trivial server for a remote keystore.

<pre>
public class FileServer {
    private static Logger logger = Logger.getLogger(FileServer.class);
    private InetAddress localAddress;
    private int port;
    private int backlog;
    private Set<String> allowedHosts = new TreeSet();
    private String fileName;
    ...
    public void call() throws Exception {
        SSLContext sslContext = SSLContexts.create(true, "fileserver.ssl", 
                System.getProperties(), new MockableConsoleAdapter(System.console()));
        SSLServerSocket serverSocket = (SSLServerSocket) sslContext.getServerSocketFactory().
                createServerSocket(port, backlog, localAddress);
        serverSocket.setNeedClientAuth(true);
        FileInputStream stream = new FileInputStream(fileName);
        int length = (int) new File(fileName).length();
        byte[] bytes = new byte[length];
        stream.read(bytes);
        while (true) {
            Socket socket = serverSocket.accept();
            logger.info("hostAddress " + socket.getInetAddress().getHostAddress());
            if (allowedHosts.contains(socket.getInetAddress().getHostAddress())) {
                socket.getOutputStream().write(bytes);
            }
            socket.close();
        }        
    }    
}
</pre>

where we create an SSLServerSocket requiring client authentication, and write out the keystore file to client connections from allowed remote hosts.

<h4>DualControlManager</h4>

Finally, we present DualControlManager which accepts the split password submissions.

<pre>
public class DualControlManager {
    private Properties properties;
    private String purpose;
    private int submissionCount;
    private SSLContext sslContext;
    private Map<String, char[]> submissions = new TreeMap();
    
    public DualControlManager(Properties properties, int submissionCount, String purpose) {
        this.properties = properties;
        this.submissionCount = submissionCount;
        this.purpose = purpose;
    }

    public void init(SSLContext sslContent) {
        this.sslContext = sslContent;
    }
    ...
}
</pre>

where we specify the required number of password submissions for some purpose e.g. the generate a key or load a key, and provide an SSLContext for the SSLServerSocket.

Our app calls DualControlManager to accept submissions via an SSLServerSocket as follows.

<pre>
    private final static int PORT = 4444;
    private final static String HOST = "127.0.0.1";
    private final static String REMOTE_ADDRESS = "127.0.0.1";
    ...
    public void call() throws Exception {
        logger.info("purpose: " + purpose);
        SSLServerSocket serverSocket = (SSLServerSocket) sslContext.
                getServerSocketFactory().createServerSocket(PORT, submissionCount,
                InetAddress.getByName(HOST));
        try {
            serverSocket.setNeedClientAuth(true);
            accept(serverSocket);
        } finally {
            serverSocket.close();
        }
        buildDualMap();
    }
</pre>

where we create a client-authenticated SSL server socket.

Note that we have hard-wired the SSLServerSocket to localhost. Therefore DualControlConsole must be invoked either in a local ssh session, or a remote session using ssh port forwarding.

<pre>
    private void accept(SSLServerSocket serverSocket) throws Exception {
        logger.info("accept: " + submissionCount);        
        while (submissions.size() < submissionCount) {
            SSLSocket socket = (SSLSocket) serverSocket.accept();
            try {
                if (!socket.getInetAddress().getHostAddress().equals(REMOTE_ADDRESS)) {
                    throw new Exception("Invalid remote address: "
                            + socket.getInetAddress().getHostAddress());
                }
                read(socket);
            } finally {
                socket.close();
            }
        }
    }
</pre>

where we accumulate the required number of submissions in a loop, handing each socket connection as follows.

<pre>
    private void read(SSLSocket socket) throws Exception {
        DataOutputStream dos = new DataOutputStream(socket.getOutputStream());
        String name = getCN(socket.getSession().getPeerPrincipal());
        if (submissions.keySet().contains(name)) {
            String errorMessage = "Duplicate submission from " + name;
            dos.writeUTF(errorMessage);
            dos.writeUTF("");
            throw new Exception(errorMessage);
        }
        dos.writeUTF("Connected " + name);        
        dos.writeUTF(purpose);
        DataInputStream dis = new DataInputStream(socket.getInputStream());
        char[] passphrase = readChars(dis);
        try {
            String resultMessage = verify(name, passphrase);
            dos.writeUTF(resultMessage);
            logger.info(resultMessage);
        } catch (Exception e) {
            dos.writeUTF(e.getMessage());
            logger.warn(e.getMessage());
            throw e;
        }
    }
</pre>

where the custodian's username is determined from their SSL cert, in particular the CN field.

DualControlGenSecKey will require DualControlManager to verify the length and complexity of new passphrases, but otherwise we'll set verifyPassphrase to false e.g. for existing passphrases as required to load the key into our app.

<pre>
    private String verify(String name, char[] passphrase) throws Exception {
        if (passphrase.length == 0) {
            return "Empty submission from " + name;
        }
        String responseMessage = "Received " + name;
        if (verifyPassphrase && !verifiedNames.contains(name)) {
            String invalidMessage = new DualControlPassphraseVerifier(properties).
                    getInvalidMessage(passphrase);
            if (invalidMessage != null) {
                throw new Exception(responseMessage + ": " + invalidMessage);
            }
        }
        submissions.put(name, passphrase);
        return responseMessage;
    }
</pre>

where an empty passhrase is sent by DualControlConsole to abort if re-entered passphrase doesn't match, and we'll let the custodian retry in that case. 

Finally, we compose a map of dual aliases and split passwords as follows.

<pre>
    private void buildDualMap() {
        for (String name : submissions.keySet()) {
            for (String otherName : submissions.keySet()) {
                if (name.compareTo(otherName) < 0) {
                    String dualAlias = String.format("%s-%s", name, otherName);
                    char[] dualPassword = combineSplitPassword(
                            submissions.get(name), submissions.get(otherName));
                    dualMap.put(dualAlias, dualPassword);
                    logger.info("dualAlias: " + dualAlias);
                }
            }
        }
        for (char[] password : submissions.values()) {
            Arrays.fill(password, (char) 0);
        }
    }
</pre>

where the compareTo() in the nested loop above ensures that we exclude tehe alphabetically-challenged evanx-brent in favour of brent-evanx, and the likes of evanx-evanx, which is just silly.

Incidently, we combine the split passwords by simply concatenating them.
<pre>
    public static char[] combineSplitPassword(char[] password, char[] other) {
        char[] splitPassword = new char[password.length + other.length + 1];
        int index = 0;
        for (char ch : password) {
            splitPassword[index++] = ch;
        }
        splitPassword[index++] = '|';
        for (char ch : other) {
            splitPassword[index++] = ch;
        }
        return splitPassword;
    }
</pre>

where we arbitrarily decided to delimit the two personal passwords with the vertical bar character.

<h4>Unit test</h4>

In <a href="https://code.google.com/p/vellum/source/browse/trunk/test/dualcontrol/DualControlTest.java">DualControlTest</a>, we test DualControlManager and DualControlConsole in concert, using threads, mock consoles and what-not. This will be presented in a follow-on article, as this article is already too long.

<h4>Crypto server</h4>

We might wish to create a central crypto server which is dual-controlled, rather than burden our app. In this case, we can restart our application without dual control, and indeed have any number of apps using this server. This simplifies key management, and enables us to isolate our keys to improve security.

<img align="right" style="margin-left: 4px" src="http://jroller.com/evanx/resource/gnome-keys-250.png" />

We'll implement the crypto server in a subsequent article. <i>Then we can say, "Dual control? We have an app for that." ;)</i>

<h4>Conclusion</h4>

The problem with encryption is secure key management. We shouldn't leave the key under the mat.

PCI requires that "split knowledge and dual control" be used to protect our data-encryption key so that no single person can extract the data in clear-text, not even our most trustworthy employee today, rogue tomorrow. Or victim of blackmail, or government coercion ;)

We introduce our DualControlGenSecKey utility for generating a new secret key. We protect this key using password-based encryption, courtesy of JceKeyStore. We enforce split knowledge of the key password, so that dual control is required to load the key. Each split password is effectively a key-encryption key split between two custodians, and so known to no single person.

We propose keeping at least three copies of the same key, but where each copy is password-protected by a different duo of custodians. Then when any one custodian is on vacation or otherwise indisposed, the other two custodians can restart our app.

We note that we require two shared passwords (server SSL keystore/truststore and secret keystore), and as well two private passwords per custodian (client SSL keystore/truststore and secret key split password), and thereby hopefully keep our secret key, erm, secret.

<h4>Furthermore</h4>

In "Dual Control Mock Console" we will present a unit test orchestrating DualControlManager and DualControlConsole threads - preview <a href="https://code.google.com/p/vellum/source/browse/trunk/test/dualcontrol/DualControlTest.java">DualControlTest.java</a>

<img align="left" style="margin-left: 4px" src="http://upload.wikimedia.org/wikipedia/commons/thumb/e/e0/Gnome-applications-office.svg/200px-Gnome-applications-office.svg.png" />

In "Dual Control Enroll" we will present tools to enroll and revoke custodians - preview <a href="https://code.google.com/p/vellum/source/browse/trunk/src/dualcontrol/DualControlEnroll.java">DualControlEnroll.java</a> and <a href="https://code.google.com/p/vellum/source/browse/trunk/src/dualcontrol/DualControlRevoke.java">DualControlRevoke.java</a>

In "Dual Control Crypto Server" we implement a dual-controlled crypto server to unburden our apps, simplify key management and enhance security - preview <a href="https://code.google.com/p/vellum/source/browse/trunk/src/dualcontrol/CryptoServer.java">CryptoServer.java</a> and its <a href="https://code.google.com/p/vellum/source/browse/trunk/src/dualcontrol/CryptoHandler.java">CryptoHandler.java</a>

In "Dual Control Key Protection" we address increased protection against brute-force password attacks e.g. via PBE of the keystore using PBKDF2 with a high number of iterations, so that the key takes a second or two to recover, rather than half a millisecond - preview <a href="https://code.google.com/p/vellum/source/browse/trunk/src/dualcontrol/JceksBruteForceTimer.java">JceksBruteForceTimer.java</a>, <a href="https://code.google.com/p/vellum/source/browse/trunk/src/dualcontrol/RecryptedKeyStore.java">RecryptedKeyStore.java</a> and <a href="https://code.google.com/p/vellum/source/browse/trunk/src/dualcontrol/AesPbeStore.java">AesPbeStore.java</a>

In "Dual Control Key Rotation" we'll address periodic key revision e.g. migrating from an older "dek2013" key to a new "dek2014" key.

You can browse the code for this exercise at <a href="http://code.google.com/p/vellum">code.google.com/vellum</a> in <a href="https://code.google.com/p/vellum/source/browse/#svn%2Ftrunk%2Fsrc%2Fdualcontrol">src/dualcontrol</a> and <a href="https://code.google.com/p/vellum/source/browse/trunk/test/dualcontrol/">test/dualcontrol</a>.

<a href="https://twitter.com/evanxsummers" class="twitter-follow-button" data-show-count="false">@evanxsummers</a>
